﻿﻿/* **********************************************************************************
 * Copyright (c) 2011 John Hughes
 *
 * j9Script is licenced under the Microsoft Reciprocal License (Ms-RL).
 *
 * Project Website: http://j9Script.codeplex.com/
 * **********************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.Specialized;
using System.Collections;
using J9Script.Collections;

namespace J9Script.Internal
{
    internal class ScriptFunctionInfoCollection
    {
        private Dictionary<string, ScriptFunctionInfo> _items = new Dictionary<string, ScriptFunctionInfo>(StringComparer.OrdinalIgnoreCase);

        //protected override string GetKeyForItem(ScriptFunctionInfo item)
        //{
        //    return item.Name.ToUpper();
        //}
        //public new bool Contains(string key)
        //{
        //    // make the search case-insensitive (we uppercased the key in GetKeyForItem())
        //    return base.Contains(key.ToUpper());
        //}
        //public new ScriptFunctionInfo this[string key]
        //{
        //    // make the get case-insensitive (we uppercased the key in GetKeyForItem())
        //    get { return base[key.ToUpper()]; }
        //}

        public void Add(ScriptFunctionInfo item)
        {
            _items.Add(getKeyName(item), item);
        }

        private string getKeyName(ScriptFunctionInfo item)
        {
            string key = item.Name;
            for (int i = 0; i < item.Arguments.Count; i++)
                key += "_" + item.Arguments[i];

            return key;
        }

        private string getKeyName(string name, IScriptObject[] args)
        {
            string key = name;
            for (int i = 0; i < args.Length; i++)
                key += "_" + args[i].TypeName;
            
            return key;
        }

        public ScriptFunctionInfo GetByNameAndArgTypes(string name, IScriptObject[] args)
        {
            string key = getKeyName(name, args);

            return _items[key];
        }

        public bool ContainsFunctionOverride(string name, IScriptObject[] args)
        {
            string key = getKeyName(name, args);

            return _items.ContainsKey(key);
        }

        /// <summary>
        /// Scans a script and finds all the function declarations.
        /// </summary>
        /// <param name="script">The script to scan.</param>
        /// <returns>A ScriptFunctionInfoCollection that contains all the found function declaration information.</returns>
        public static ScriptFunctionInfoCollection CreateFromScript(string script)
        {
            // function Number Add(Number value1, Number value2)
            // {
            //   total = value1 + value2;
            //   return total;
            // }

            // This doesn't check for syntax errors in the script except in the function declaration.

            ScriptFunctionInfoCollection functions = new ScriptFunctionInfoCollection();

            ScriptParser parser = new ScriptParser(script);

            Token token = parser.GetToken();
            while (!token.IsEndOfScript)
            {
                if (token.TokenType == TokenTypeEnum.Keyword && token.TokenString.ToUpper() == "FUNCTION")
                {
                    Token functionKeyword = token;

                    // Get function return type
                    Token returnType = parser.GetToken();

                    // Get function name
                    Token functionName = parser.GetToken();
                    if (functionName.TokenType != TokenTypeEnum.Identifier)
                        throw new ScriptEvaluatorException("Function is missing a return type.", functionKeyword.Position, parser.IdentifyScriptPosition(functionKeyword.Position));

                    // Get opening parenthesis
                    if (parser.GetToken().TokenString != "(")
                        throw new ScriptEvaluatorException("Function '" + functionName.TokenString + "' is missing '('.", parser.Position, parser.IdentifyScriptPosition(functionKeyword.Position));


                    // Get Arguments
                    NamedIndexedDictionary<string> arguments = new NamedIndexedDictionary<string>(true, false);
                    Token argTypeName = parser.GetToken();
                    while (argTypeName.TokenType != TokenTypeEnum.Operator && !argTypeName.IsEndOfScript && !argTypeName.IsEndOfStatement)  // while the token is not the ")" operator
                    {
                        Token argName = parser.GetToken();
                        try
                        {
                            arguments.Add(argName.TokenString, argTypeName.TokenString);
                        }
                        catch (Exception ex)
                        {
                            // It'll be a System.ArgumentException
                            throw new ScriptEvaluatorException("The parameter name '" + argName.TokenString + "' is a duplicate.", argName.Position, parser.IdentifyScriptPosition(argName.Position));
                        }
                        
                        argTypeName = parser.GetToken();
                        if (argTypeName.TokenString == ",")
                            argTypeName = parser.GetToken();
                    }


                    // Get closing parenthesis
                    if (argTypeName.TokenString != ")")
                        throw new ScriptEvaluatorException("Function '" + functionName.TokenString + "' is missing ')'.", parser.Position, parser.IdentifyScriptPosition(functionName.Position));

                    // Get opening curly brackets
                    Token blockStart = parser.GetToken();
                    if (blockStart.TokenString != "{")
                        throw new ScriptEvaluatorException("Function '" + functionName.TokenString + "' is missing '{'.", parser.Position, parser.IdentifyScriptPosition(functionName.Position));

                    // Verify return type
                    // TODO: maybe do this outside of here since we don't know the types in here.


                    ScriptFunctionInfo function = new ScriptFunctionInfo(functionName.TokenString, returnType.TokenString, blockStart.Position, arguments);
                    try
                    {
                        functions.Add(function);
                    }
                    catch (Exception ex)
                    {
                        throw new ScriptEvaluatorException("A function named '" + functionName.TokenString + "' with matching argument types already exists.", functionKeyword.Position, parser.IdentifyScriptPosition(functionKeyword.Position));
                    }
                }

                token = parser.GetToken();
            }

            return functions;
        }
    }

    internal class ScriptFunctionInfo
    {
        public string Name;
        public string ReturnTypeName;
        public ScriptPosition BlockStartPosition;
        public NamedIndexedDictionary<string> Arguments; //TODO: I would like to see NamedIndexedDictionary replaced with a possible built-in .net class.

        public ScriptFunctionInfo(string name, string returnTypeName, ScriptPosition position, NamedIndexedDictionary<string> arguments)
        {
            Name = name;
            ReturnTypeName = returnTypeName;
            BlockStartPosition = position;
            Arguments = arguments;
        }
    }
}
